# encoding: utf-8
"""
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    created by lei.chang on '18/12/2024'
    comment: 飞书平台相关
"""
import asyncio
import time

import aiohttp
from pydantic import Field
from laner.pydantic import BaseModel

from config import Config
from project.core.exception import BizError
from project.lib.common import logger


class TaskResult(BaseModel):
    """ 文件导出结果
    """
    file_extension: str = Field('', title='文件扩展名')
    type: str = Field('', title='文件类型')
    file_name: str = Field('', title='文件名称')
    file_token: str = Field('', title='文件唯一标识')
    file_size: int = Field(0, title='文件大小')
    job_error_msg: str = Field('success', title='任务错误信息')
    job_status: str = Field(0, title='任务状态')
    binary_stream: bytes = Field(None, title='文件二进制流')


class FeiShu:

    @staticmethod
    def get_headers(access_token=None):
        """
        :return:
        """
        headers = {'Content-Type': 'application/json; charset=utf-8'}
        if access_token:
            headers.update({'Authorization': f'Bearer {access_token}'})

        return headers

    @staticmethod
    async def get_tenant_access_token():
        """ 取得飞书tenant_access_token
        :return:
        """
        url = 'https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal'

        async with aiohttp.ClientSession() as session:
            async with session.post(url, headers=FeiShu.get_headers(), json=Config.FEISHU_OPTIONS) as resp:
                if resp.status != 200:
                    error = f'获取飞书tenant_access_token失败 status: {resp.status} content: {(await resp.content.read())}'
                    logger.error(message=error)
                    raise BizError(message=error)

                result = await resp.json()
                if result.get('code') != 0:
                    error = f'获取飞书tenant_access_token失败 msg: {result.get("msg")}'
                    logger.error(message=error)
                    raise BizError(message=error)

        return result.get('tenant_access_token')

    @staticmethod
    async def create_export_task(file_extension, token, _type, access_token, sub_id=None):
        """ 创建文件导出任务
        :param file_extension: 将云文档导出为本地文件后，本地文件的扩展名。
        :param token: 要导出的云文档的 token。
        :param _type: 要导出的云文档的类型 。可通过云文档的链接判断。
                      sheet: 飞书电子表格。支持导出扩展名为 xlsx 和 csv 的文件。
                      bitable: 飞书多维表格。支持导出扩展名为 xlsx 和 csv 格式的文件。
                      docx: 新版飞书文档。支持导出扩展名为 docx 和 pdf 格式的文件。
        :param sub_id: 导出飞书电子表格或多维表格为 CSV 文件时，需传入电子表格工作表的 ID 或多维表格数据表的 ID：
                      电子表格可调用获取工作表 接口获取返回的 sheet_id 的值作为该参数的值
                      多维表格可调用列出数据表接口获取返回的 table_id 的值作为该参数的值
        :param access_token:
        :return:
            {
                "code": 0,
                "msg": "success",
                "data": {
                    "ticket": "6933093124755423251"
                }
            }
        """
        url = 'https://open.feishu.cn/open-apis/drive/v1/export_tasks'

        params = {
            'file_extension': file_extension,
            'token': token,
            'type': _type
        }
        if sub_id is not None:
            params['sub_id'] = sub_id

        async with aiohttp.ClientSession() as session:
            async with session.post(url, headers=FeiShu.get_headers(access_token), json=params) as resp:
                if resp.status != 200:
                    error = f'创建飞书文档导出任务失败 status: {resp.status} content: {(await resp.content.read())}'
                    logger.error(message=error)
                    raise BizError(message=error)

                result = await resp.json()
                if result.get('code') != 0:
                    error = f'创建飞书文档导出任务失败 msg: {result.get("msg")}'
                    logger.error(message=error)
                    raise BizError(message=error)

        return result['data']['ticket']

    @staticmethod
    async def get_export_task_result(ticket, token, access_token, time_out=60) -> TaskResult:
        """ 得到文件导出任务结果
        :param ticket:
        :param token:
        :param access_token:
        :param time_out:
        :return:
            {
                "code": 0,
                "msg": "success",
                "data": {
                    "result": {
                        "file_extension": "pdf",
                        "type": "doc",
                        "file_name": "docName",
                        "file_token": "boxcnxe5OdjlAkNgSNdsJvabcef",
                        "file_size": 34356,
                        "job_error_msg": "success",
                        "job_status": 0
                    }
                }
            }
        """
        url = f'https://open.feishu.cn/open-apis/drive/v1/export_tasks/{ticket}'
        params = {
            'token': token
        }

        start_stamp = int(time.time())
        while True:
            if int(time.time()) - start_stamp >= 60:
                error = f'获取飞书云文档导出结果超时 timeout: {time_out}'
                logger.error(message=error)
                raise BizError(message=error)

            async with aiohttp.ClientSession() as session:
                async with session.get(url, headers=FeiShu.get_headers(access_token), params=params) as resp:
                    if resp.status != 200:
                        error = f'获取飞书云文档导出结果失败 status: {resp.status} content: {(await resp.content.read())}'
                        logger.error(message=error)
                        raise BizError(message=error)

                    result = await resp.json()
                    if result.get('code') != 0:
                        error = f'获取飞书云文档导出结果失败 msg: {result.get("msg")}'
                        logger.error(message=error)
                        raise BizError(message=error)

                    ret_data = result['data']['result']
                    if ret_data:
                        if ret_data['job_status'] == 0:
                            break
                        elif ret_data['job_status'] == 3:
                            error = f'获取飞书云文档导出结果失败 job_error_msg: {ret_data["job_error_msg"]}'
                            logger.error(message=error)
                            raise BizError(message=error)
                        elif ret_data['job_status'] == 107:
                            error = f'获取飞书云文档导出结果失败(文件过大) file_size: {ret_data["file_size"]}'
                            logger.error(message=error)
                            raise BizError(message=error)

            await asyncio.sleep(1)

        return TaskResult(**ret_data)

    @staticmethod
    async def download_export_file(file_token, access_token):
        """ 下载导出文件
        :param file_token:
        :param access_token:
        :return:
            文件二进制流
        """
        url = f'https://open.feishu.cn/open-apis/drive/v1/export_tasks/file/{file_token}/download'

        async with aiohttp.ClientSession() as session:
            async with session.get(url, headers=FeiShu.get_headers(access_token)) as resp:
                if resp.status != 200:
                    error = f'获取飞书tenant_access_token失败 status: {resp.status} content: {(await resp.content.read())}'
                    logger.error(message=error)
                    raise BizError(message=error)

                stream = await resp.read()

        return stream

    @staticmethod
    async def get_sheets(token, access_token):
        """ 获取工作表列表
        :param token:
        :param access_token:
        :return:
        """
        url = f'https://open.feishu.cn/open-apis/sheets/v3/spreadsheets/{token}/sheets/query'

        async with aiohttp.ClientSession() as session:
            async with session.get(url, headers=FeiShu.get_headers(access_token)) as resp:
                if resp.status != 200:
                    error = f'获取飞书工作表列表失败 status: {resp.status} content: {(await resp.content.read())}'
                    logger.error(message=error)
                    raise BizError(message=error)
                result = await resp.json()
                if result.get('code', 0) != 0:
                    error = f'获取飞书工作表列表失败 msg: {result.get("msg")}'
                    logger.error(message=error)
                    raise BizError(message=error)

        return result['data']['sheets']

    @staticmethod
    async def get_tables(token, access_token):
        """ 获取工作表列表
        :param token:
        :param access_token:
        :return:
        """
        url = f'https://open.feishu.cn/open-apis/bitable/v1/apps/{token}/tables'

        async with aiohttp.ClientSession() as session:
            async with session.get(url, headers=FeiShu.get_headers(access_token)) as resp:
                if resp.status != 200:
                    error = f'获取飞书工作表列表失败 status: {resp.status} content: {(await resp.content.read())}'
                    logger.error(message=error)
                    raise BizError(message=error)
                result = await resp.json()
                if result.get('code', 0) != 0:
                    error = f'获取飞书工作表列表失败 msg: {result.get("msg")}'
                    logger.error(message=error)
                    raise BizError(message=error)

        return result['data']['items']


if __name__ == "__main__":
    """
    """
    access_token = asyncio.run(FeiShu.get_tenant_access_token())

    tables = asyncio.run(FeiShu.get_sheets('Izqws5xb0hzKcpt3LfvckTNBnNg', access_token))

    print(tables)
